#include "Socket.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <iostream>
#include <string.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include "Hmac.h"
#define ERROR -1
#define DESCONECTADO 0
#define ENV_OK 1
#define REC_OK 1

Socket::Socket()
{
	this->sockfd = socket(AF_INET, SOCK_STREAM, 0);
	this->conected = false;	
}

int Socket::escuchar(const int &puerto)
{
	struct sockaddr_in dir_orig;
	//Completo la estructura con la direccion de origen
	dir_orig.sin_family = AF_INET;
	dir_orig.sin_port = htons(puerto);
	dir_orig.sin_addr.s_addr = INADDR_ANY;
	memset(&(dir_orig.sin_zero), 8, sizeof(int));

	int yes = 1;
	int res = setsockopt(this->sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));

	if (res != ERROR) {
		res = bind(this->sockfd, (struct sockaddr *) &dir_orig,
				sizeof(struct sockaddr));

		if (res != ERROR)
			res = listen(this->sockfd, 5);
	}
	return res;
}

Socket* Socket::aceptar()
{
	struct sockaddr_in dir_cli;
	socklen_t len = sizeof(struct sockaddr);
	int sockfd_cli = accept(this->sockfd, (struct sockaddr *) &dir_cli, &len);

	Socket* cliente = NULL;

	if (sockfd_cli != ERROR)
		cliente = new Socket(sockfd_cli);

	return cliente;
}

int Socket::enviar(const Json::Value& json, const std::string& clave)
{
	Json::StyledWriter writer;
	std::string msj = writer.write(json);
	uint32_t tam = msj.length();
	convertir(tam);
	std::string subStr = msj;

    concatenarHmac(subStr, clave);
	subStr.insert(0, (char*)&tam, 4);
    

	int bytes_env = 0;
	int len;
	do {
		subStr = subStr.substr(bytes_env);
		len = subStr.length();
		bytes_env = send(sockfd, subStr.c_str(), len, 0);
		if (bytes_env == ERROR){return ERROR;}

	} while (bytes_env != len);

	return ENV_OK;
}

int Socket::recibir(Json::Value& json, const std::string& clave)
{
	uint32_t tam;
	char *buf_tam = (char*) &tam;
	unsigned bytes_rec_tot = 0;

	do {
		int bytes_rec = recv(sockfd, buf_tam+bytes_rec_tot, 4-bytes_rec_tot, 0);

		if (bytes_rec == ERROR)
			return ERROR;
		else if (bytes_rec == DESCONECTADO)
			return DESCONECTADO;

		bytes_rec_tot += bytes_rec;
	} while (bytes_rec_tot < 4);

	convertir(tam);

	std::string msj = "";
	char buf_msj[256];
	bytes_rec_tot = 0;
	do {
		int bytes_rec;
		if((tam-bytes_rec_tot) >= 256){
			bytes_rec = recv(sockfd, buf_msj, 256, 0);
		} else {
			bytes_rec = recv(sockfd, buf_msj, tam-bytes_rec_tot, 0);
		}

		if (bytes_rec == ERROR)	return ERROR;
		else if (bytes_rec == DESCONECTADO)
			return DESCONECTADO;

		msj.append(buf_msj, bytes_rec);
		bytes_rec_tot += bytes_rec;
	} while (bytes_rec_tot < tam);


	if(!autenticarMsj(msj, clave))
		return ERROR;

	Json::Reader reader;
	reader.parse(msj, json);
 
	return REC_OK;
}

void Socket::convertir(uint32_t& tam)
{
	uint32_t aux = 0x1;
	if(*(char*)&aux == 1)
		//si la maquina trabaja en little endian invierto los bytes
		tam = htobe32(tam);
}

Socket::Socket(int sockfd): sockfd(sockfd)
{
	this->conected = false;
}

int Socket::shutDown(int how)
{
	this->conected = false; 	
	return shutdown(this->sockfd, how);
}

Socket::~Socket()
{
	close(this->sockfd);
}

int Socket::conectar(const std::string& ip, const int& puerto)
{
	struct sockaddr_in dir_dest;
	struct hostent *he;
	he = gethostbyname(ip.c_str());
	// Completo la estructura de direccion de destino
	dir_dest.sin_family = AF_INET;
	dir_dest.sin_port = htons(puerto);
	dir_dest.sin_addr = *((struct in_addr*)he->h_addr);
	memset(&(dir_dest.sin_zero), 8, sizeof(int));

	int res = connect(this->sockfd, (struct sockaddr *) &dir_dest,
	sizeof(struct sockaddr));
	if (res!=-1){
		this->conected = true;
	}
	return res; 
}

bool Socket::conectado(){
	return this->conected;
}

void Socket::concatenarHmac(std::string& subStr, const std::string& clave)
{
	Hmac hmac;
	std::string digest = hmac.getDigest(subStr, clave);

	subStr.append(digest.c_str(), TAMANIO_DIGEST_MD5);
}

bool Socket::autenticarMsj(const std::string& msj, const std::string& clave)
{
	Hmac hmac;
    std::string digest = hmac.getDigest(msj, clave);

	
	char buf_digest[TAMANIO_DIGEST_MD5];
	unsigned bytes_rec_tot = 0;
	do {
		int bytes_rec = recv(sockfd, buf_digest+bytes_rec_tot, TAMANIO_DIGEST_MD5-bytes_rec_tot, 0);

		if (bytes_rec == ERROR)
			return ERROR;
		else if (bytes_rec == DESCONECTADO)
			return DESCONECTADO;

		bytes_rec_tot += bytes_rec;
	} while (bytes_rec_tot < TAMANIO_DIGEST_MD5);
	
	std::string digestRecibido;
	digestRecibido.append(buf_digest, TAMANIO_DIGEST_MD5);

	return (digest == digestRecibido);
}
